home *** CD-ROM | disk | FTP | other *** search
/ The Atari Compendium / The Atari Compendium (Toad Computers) (1994).iso / files / prgtools / mint / mint96sb.zoo / src / tosfs.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-10-21  |  30.3 KB  |  1,315 lines

  1. /*
  2. Copyright 1991,1992 Eric R. Smith. All rights reserved.
  3. */
  4.  
  5. /* a VERY simple tosfs.c 
  6.  * this one is extremely brain-damaged, but will serve OK for a
  7.  * skeleton in which to put a "real" tosfs.c
  8.  */
  9.  
  10. #include "mint.h"
  11.  
  12. /* search mask for anything OTHER THAN a volume label */
  13. #define FILEORDIR 0x37
  14.  
  15. char tmpbuf[PATH_MAX+1];
  16.  
  17. static long    ARGS_ON_STACK tos_root    P_((int drv, fcookie *fc));
  18. static long    ARGS_ON_STACK tos_lookup    P_((fcookie *dir, const char *name, fcookie *fc));
  19. static long    ARGS_ON_STACK tos_getxattr    P_((fcookie *fc, XATTR *xattr));
  20. static long    ARGS_ON_STACK tos_chattr    P_((fcookie *fc, int attrib));
  21. static long    ARGS_ON_STACK tos_chown    P_((fcookie *fc, int uid, int gid));
  22. static long    ARGS_ON_STACK tos_chmode    P_((fcookie *fc, unsigned mode));
  23. static long    ARGS_ON_STACK tos_mkdir    P_((fcookie *dir, const char *name, unsigned mode));
  24. static long    ARGS_ON_STACK tos_rmdir    P_((fcookie *dir, const char *name));
  25. static long    ARGS_ON_STACK tos_remove    P_((fcookie *dir, const char *name));
  26. static long    ARGS_ON_STACK tos_getname    P_((fcookie *root, fcookie *dir, char *pathname));
  27. static long    ARGS_ON_STACK tos_rename    P_((fcookie *olddir, char *oldname,
  28.                     fcookie *newdir, const char *newname));
  29. static long    ARGS_ON_STACK tos_opendir    P_((DIR *dirh, int flags));
  30. static long    ARGS_ON_STACK tos_readdir    P_((DIR *dirh, char *nm, int nmlen, fcookie *));
  31. static long    ARGS_ON_STACK tos_rewinddir    P_((DIR *dirh));
  32. static long    ARGS_ON_STACK tos_closedir    P_((DIR *dirh));
  33. static long    ARGS_ON_STACK tos_pathconf    P_((fcookie *dir, int which));
  34. static long    ARGS_ON_STACK tos_dfree    P_((fcookie *dir, long *buf));
  35. static long    ARGS_ON_STACK tos_writelabel    P_((fcookie *dir, const char *name));
  36. static long    ARGS_ON_STACK tos_readlabel    P_((fcookie *dir, char *name, int namelen));
  37.  
  38. static long    ARGS_ON_STACK tos_creat    P_((fcookie *dir, const char *name, unsigned mode,
  39.                     int attrib, fcookie *fc));
  40. static DEVDRV *    ARGS_ON_STACK tos_getdev    P_((fcookie *fc, long *devsp));
  41. static long    ARGS_ON_STACK tos_open    P_((FILEPTR *f));
  42. static long    ARGS_ON_STACK tos_write    P_((FILEPTR *f, const char *buf, long bytes));
  43. static long    ARGS_ON_STACK tos_read    P_((FILEPTR *f, char *buf, long bytes));
  44. static long    ARGS_ON_STACK tos_lseek    P_((FILEPTR *f, long where, int whence));
  45. static long    ARGS_ON_STACK tos_ioctl    P_((FILEPTR *f, int mode, void *buf));
  46. static long    ARGS_ON_STACK tos_datime    P_((FILEPTR *f, short *time, int rwflag));
  47. static long    ARGS_ON_STACK tos_close    P_((FILEPTR *f, int pid));
  48. static long    ARGS_ON_STACK tos_dskchng    P_((int drv));
  49.  
  50. /* some routines from biosfs.c */
  51. extern long    ARGS_ON_STACK null_select    P_((FILEPTR *f, long p, int mode));
  52. extern void    ARGS_ON_STACK null_unselect    P_((FILEPTR *f, long p, int mode));
  53.  
  54. DEVDRV tos_device = {
  55.     tos_open, tos_write, tos_read, tos_lseek, tos_ioctl, tos_datime,
  56.     tos_close, null_select, null_unselect
  57. };
  58.  
  59. FILESYS tos_filesys = {
  60.     (FILESYS *)0,
  61.     FS_KNOPARSE | FS_NOXBIT,
  62.     tos_root,
  63.     tos_lookup, tos_creat, tos_getdev, tos_getxattr,
  64.     tos_chattr, tos_chown, tos_chmode,
  65.     tos_mkdir, tos_rmdir, tos_remove, tos_getname, tos_rename,
  66.     tos_opendir, tos_readdir, tos_rewinddir, tos_closedir,
  67.     tos_pathconf, tos_dfree, tos_writelabel, tos_readlabel,
  68.     nosymlink, noreadlink, nohardlink, nofscntl, tos_dskchng
  69. };
  70.  
  71. /* some utility functions and variables: see end of file */
  72. static DTABUF     *lastdta;    /* last DTA buffer we asked TOS about */
  73. static DTABUF    foo;
  74. static void do_setdta P_((DTABUF *dta));
  75. static int executable_extension P_((char *));
  76.  
  77. /* this array keeps track of which drives have been changed */
  78. /* a nonzero entry means that the corresponding drive has been changed,
  79.  * but GEMDOS doesn't know it yet
  80.  */
  81. static char drvchanged[NUM_DRIVES];
  82.  
  83. /* force TOS to see a media change */
  84. static void force_mediach P_((int drv));
  85. static long ARGS_ON_STACK Newgetbpb P_((int));
  86. static long ARGS_ON_STACK Newmediach P_((int));
  87. static long ARGS_ON_STACK Newrwabs P_((int, void *, int, int, int, long));
  88.  
  89. #define NUM_INDICES 128
  90. #define MIN_AGE 8
  91.  
  92. struct tindex {
  93.     char *name;        /* full path name */
  94.     FILEPTR *open;        /* fileptrs for this file; OR
  95.                  * count of number of open directories
  96.                  */
  97.     LOCK *locks;        /* locks on this file */
  98. /* file status */
  99.     long  size;
  100.     short time;
  101.     short date;
  102.     short attr;
  103.     short valid;        /* 1 if the above status is still valid */
  104.     short stamp;        /* age of this index, for garbage collection */
  105. } gl_ti[NUM_INDICES];
  106.  
  107. /* temporary index for files found by readdir */
  108. static struct tindex tmpindex;
  109. static char tmpiname[PATH_MAX];
  110.  
  111. static struct tindex *tstrindex P_((char *s));
  112. static int tfullpath P_((char *result, struct tindex *base, const char *name));
  113. static struct tindex *garbage_collect P_((void));
  114.  
  115. static short tclock;        /* #calls to tfullpath since last garbage
  116.                    collection */
  117.  
  118. /* some extra flags for the attr field */
  119.  
  120. /*
  121.  * is a string the name of a file with executable extension?
  122.  */
  123. #define FA_EXEC 0x4000
  124. /*
  125.  * should the file be deleted when it is closed?
  126.  */
  127. #define FA_DELETE 0x2000
  128.  
  129. /*
  130.  * NOTE: call executable_extension only on a DTA name returned from
  131.  * Fsfirst(), not on an arbitrary path
  132.  */
  133.  
  134. static int
  135. executable_extension(s)
  136.     char *s;
  137. {
  138.     while (*s && *s != '.') s++;
  139.     if (!*s) return 0;
  140.     s++;
  141.     if (s[0] == 'T') {
  142.         return (s[1] == 'T' && s[2] == 'P') ||
  143.                (s[1] == 'O' && s[2] == 'S');
  144.     }
  145.     if (s[0] == 'P')
  146.         return s[1] == 'R' && s[2] == 'G';
  147.     if (s[0] == 'A')
  148.         return s[1] == 'P' && s[2] == 'P';
  149.     if (s[0] == 'G')
  150.         return s[1] == 'T' && s[2] == 'P';
  151.     return 0;
  152. }
  153.  
  154. /*
  155.  * Look in the table of tos indices to see if an index corresponding
  156.  * to this file name already exists. If so, mark it as being used
  157.  * and return it. If not, find an empty slot and make an index for
  158.  * this string. If no empty slots exist, garbage collect and
  159.  * try again.
  160.  *
  161.  * This routine is pretty dumb; we really should use a hash table
  162.  * of some sort
  163.  */
  164.  
  165. static struct tindex *tstrindex(s)
  166.     char *s;
  167. {
  168.     int i;
  169.     char *r;
  170.     struct tindex *t, *free = 0;
  171.  
  172.     assert(s != 0);
  173.     t = gl_ti;
  174.     for (i = 0; i < NUM_INDICES; i++, t++) {
  175.         if (t->name && !stricmp(t->name, s)) {
  176.             t->stamp = tclock;    /* update use time */
  177.             return t;
  178.         }
  179.         else if (!t->name && !free)
  180.             free = t;
  181.     }
  182.     if (!free) {
  183.         free = garbage_collect();
  184.     }
  185.     if (!free) {
  186.         FATAL("tosfs: unable to get a file name index");
  187.     }
  188.     r = kmalloc((long)strlen(s)+1);
  189.     if (!r) {
  190.         FATAL("tosfs: unable to allocate space for a file name");
  191.     }
  192.     strcpy(r, s);
  193.     free->name = r;
  194.     free->stamp = tclock;
  195.     free->open = 0;
  196.     free->locks = 0;
  197.  
  198. /* check to see if this file was recently returned by opendir() */
  199.     if (tmpindex.valid && tclock - tmpindex.stamp < MIN_AGE &&
  200.         !stricmp(free->name, tmpindex.name)) {
  201.         free->size = tmpindex.size;
  202.         free->time = tmpindex.time;
  203.         free->date = tmpindex.date;
  204.         free->attr = tmpindex.attr;
  205.         free->valid = 1;
  206.         tmpindex.valid = 0;
  207.     } else
  208.         free->valid = 0;
  209.     return free;
  210. }
  211.  
  212. /*
  213.  * garbage collection routine: for any TOS index older than MIN_AGE,
  214.  * check through all current processes to see if it's in use. If
  215.  * not, free the corresponding string.
  216.  * Returns: a pointer to a newly freed index, or NULL.
  217.  */
  218.  
  219. /* it's unlikely that the kernel would need to hold onto a file cookie
  220.    for longer than this many calls to tstrindex() without first
  221.    saving the cookie in a directory or file pointer
  222.  */
  223.  
  224. static struct tindex *
  225. garbage_collect()
  226. {
  227.     struct tindex *free, *t;
  228.     fcookie *fc, *gc;
  229.     PROC *p;
  230.     int i, j;
  231.     int age;
  232.  
  233.     free = 0;
  234.     t = gl_ti;
  235.     for (i = 0; i < NUM_INDICES; i++,t++) {
  236.         if (!t->name) continue;
  237.         age = tclock - t->stamp;
  238.         t->stamp = 0;
  239.         assert(age >= 0);
  240.         if (age > MIN_AGE) {
  241.         /* see if any process is using this index */
  242.             if (t->open)
  243.                 goto found_index;
  244.             for (p = proclist; p; p = p->gl_next) {
  245.                 fc = p->curdir;
  246.                 gc = p->root;
  247.                 for (j = 0; j < NUM_DRIVES; j++,fc++,gc++) {
  248.                     if (( fc->fs == &tos_filesys &&
  249.                           fc->index == (long)t ) ||
  250.                         ( gc->fs == &tos_filesys &&
  251.                           gc->index == (long)t ) )
  252.                         goto found_index;
  253.                 }
  254.             }
  255.         /* here, we couldn't find the index in use by any proc. */
  256.             kfree(t->name);
  257.             t->name = 0;
  258.             if (!free)
  259.                 free = t;
  260.         found_index:
  261.             ;
  262.         } else {
  263.     /* make sure that future garbage collections might look at this file */
  264.             t->stamp = -age;
  265.         }
  266.     }
  267.  
  268.     tclock = 0;    /* reset the clock */
  269.     tmpindex.valid = 0; /* expire the temporary Fsfirst buffer */
  270.     return free;
  271. }
  272.  
  273. #define DIRSEP(c) ((c) == '\\')
  274.  
  275. static int
  276. tfullpath(result, basei, path)
  277.     char *result;
  278.     struct tindex *basei;
  279.     const char *path;
  280. {
  281. #define TNMTEMP 32
  282.     char *n, name[TNMTEMP+1];
  283.     int namelen, pathlen;
  284.     char *base = basei->name;
  285.     int r = 0;
  286.  
  287.     basei->stamp = ++tclock;
  288.     if (tclock > 10000) {
  289.     /* garbage collect every so often whether we need it or not */
  290.         (void)garbage_collect();
  291.     }
  292.     if (!*path) {
  293.         strncpy(result, base, PATH_MAX-1);
  294.         return r;
  295.     }
  296.  
  297.     strncpy(result, base, PATH_MAX-1);
  298.  
  299.     pathlen = strlen(result);
  300.  
  301. /* now path is relative to what's currently in "result" */
  302.  
  303.     while(*path) {
  304. /* get next name in path */
  305.         n = name; namelen = 0;
  306.         while (*path && !DIRSEP(*path)) {
  307. /* BUG: we really should to the translation to DOS 8.3
  308.  * format *here*, so that really long names are truncated
  309.  * correctly.
  310.  */
  311.             if (namelen < TNMTEMP) {
  312.                 *n++ = toupper(*path); path++; namelen++;
  313.             }
  314.             else
  315.                 path++;
  316.         }
  317.         *n = 0;
  318.         while (DIRSEP(*path)) path++;
  319. /* check for "." and ".." */
  320.         if (!strcmp(name, ".")) continue;
  321.         if (!strcmp(name, "..")) {
  322.             n = strrchr(result, '\\');
  323.             if (n) {
  324.                 *n = 0;
  325.                 pathlen = (int)(n - result);
  326.             }
  327.             else r = EMOUNT;
  328.             continue;
  329.         }
  330.         if (pathlen + namelen < PATH_MAX - 1) {
  331.             strcat(result, "\\");
  332.             pathlen++;
  333.  
  334.     /* make sure the name is restricted to DOS 8.3 format */
  335.             for (base = result; *base; base++)
  336.                 ;
  337.             n = name;
  338.             namelen = 0;
  339.             while (*n && *n != '.' && namelen++ < 8) {
  340.                 *base++ = *n++;
  341.                 pathlen++;
  342.             }
  343.             while (*n && *n != '.') n++;
  344.             if (*n == '.' && *(n+1) != 0) {
  345.                 *base++ = *n++;
  346.                 pathlen++;
  347.                 namelen = 0;
  348.                 while (*n && namelen++ < 3) {
  349.                     *base++ = *n++;
  350.                     pathlen++;
  351.                 }
  352.             }
  353.             *base = 0;
  354.         }
  355.     }
  356.     return r;
  357. }
  358.  
  359. static long ARGS_ON_STACK 
  360. tos_root(drv, fc)
  361.     int drv;
  362.     fcookie *fc;
  363. {
  364.     struct tindex *ti;
  365.  
  366.     ksprintf(tmpbuf, "%c:", drv+'A');
  367.     fc->fs = &tos_filesys;
  368.     fc->dev = drv;
  369.     ti = tstrindex(tmpbuf);
  370.     ti->size = ti->date = ti->time = 0;
  371.     ti->attr = FA_DIR;
  372.     ti->valid = 1;
  373.     fc->index = (long)ti;
  374.  
  375. /* if the drive has changed, make sure GEMDOS knows it! */
  376.     if (drvchanged[drv]) {
  377.         force_mediach(drv);
  378.     }
  379.     return 0;
  380. }
  381.  
  382. static long ARGS_ON_STACK 
  383. tos_lookup(dir, name, fc)
  384.     fcookie *dir;
  385.     const char *name;
  386.     fcookie *fc;
  387. {
  388.     long r;
  389.     struct tindex *ti = (struct tindex *)dir->index;
  390.  
  391.     r = tfullpath(tmpbuf, ti, name);
  392.  
  393. /* if the name is empty or otherwise trivial, just return the directory */
  394.     if (!strcmp(ti->name, tmpbuf)) {
  395.         *fc = *dir;
  396.         return r;
  397.     }
  398.  
  399. /* is there already an index for this file?? If so, is it up to date?? */
  400.     ti = tstrindex(tmpbuf);
  401.     if (!ti->valid) {
  402.         if (tmpbuf[1] == ':' && tmpbuf[2] == 0) {
  403.             /* a root directory -- lookup always succeeds */
  404.             foo.dta_size = 0;
  405.             foo.dta_date = foo.dta_time = 0;
  406.             foo.dta_attrib = FA_DIR;
  407.             foo.dta_name[0] = 0;
  408.         } else {
  409.             do_setdta(&foo);
  410.             r = Fsfirst(tmpbuf, FILEORDIR);
  411.             if (r) {
  412. DEBUG(("tos_lookup: Fsfirst(%s) returned %ld", tmpbuf, r));
  413.                 return r;
  414.             }
  415.         }
  416.         ti->size = foo.dta_size;
  417.         ti->date = foo.dta_date;
  418.         ti->time = foo.dta_time;
  419.         ti->attr = foo.dta_attrib;
  420.         if (executable_extension(foo.dta_name))
  421.             ti->attr |= FA_EXEC;
  422.         ti->valid = 1;
  423.     }
  424.     fc->fs = &tos_filesys;
  425.     fc->index = (long)ti;
  426.     fc->dev = dir->dev;
  427.     return r;
  428. }
  429.  
  430. static long ARGS_ON_STACK 
  431. tos_getxattr(fc, xattr)
  432.     fcookie *fc;
  433.     XATTR *xattr;
  434. {
  435.     struct tindex *ti = (struct tindex *)fc->index;
  436.     long r;
  437.     static long junkindex = 0;
  438.  
  439.     xattr->index = junkindex++;
  440.     xattr->dev = fc->dev;
  441.     xattr->nlink = 1;
  442.     xattr->uid = xattr->gid = 0;
  443.  
  444.     ti->stamp = ++tclock;
  445.     if (!ti->valid) {
  446.         do_setdta(&foo);
  447.         if (ti->name[2] == 0) {        /* a root directory */
  448. /* actually, this can also happen if a program tries to open a file
  449.  * with an empty name... so we should fail gracefully
  450.  */
  451.             goto around;
  452.         }
  453.     
  454.         r = Fsfirst(ti->name, FILEORDIR);
  455.         if (r < 0)
  456.             FATAL("tosfs: search error on [%s]", ti->name);
  457.         ti->size = foo.dta_size;
  458.         ti->date = foo.dta_date;
  459.         ti->time = foo.dta_time;
  460.         ti->attr = foo.dta_attrib;
  461.         if (executable_extension(foo.dta_name))
  462.             ti->attr |= FA_EXEC;
  463. around:
  464.         ti->valid = 1;
  465.     }
  466.     xattr->size = ti->size;
  467.  
  468. /* BUG: blksize isn't accurate if the sector size is not 512 */
  469.     xattr->blksize = 1024;
  470.     xattr->nblocks = (xattr->size + 1023) / 1024;
  471.     xattr->mdate = xattr->cdate = xattr->adate = ti->date;
  472.     xattr->mtime = xattr->ctime = xattr->atime = ti->time;
  473.     xattr->mode = (ti->attr & FA_DIR) ? (S_IFDIR | DEFAULT_DIRMODE) :
  474.              (S_IFREG | DEFAULT_MODE);
  475.  
  476.     if (ti->attr & FA_RDONLY) {
  477.         xattr->mode &= ~(S_IWUSR|S_IWGRP|S_IWOTH);
  478.     }
  479.  
  480.     if (ti->attr & FA_EXEC) {
  481.         xattr->mode |= (S_IXUSR|S_IXGRP|S_IXOTH);
  482.     }
  483.     xattr->attr = ti->attr & 0xff;
  484.     return 0;
  485. }
  486.  
  487. static long ARGS_ON_STACK
  488. tos_chattr(fc, attrib)
  489.     fcookie *fc;
  490.     int attrib;
  491. {
  492.     struct tindex *ti = (struct tindex *)fc->index;
  493.  
  494.     if (ti->attr & FA_DIR) {
  495.         DEBUG(("error: attempt to change attributes of a directory"));
  496.         return EACCDN;
  497.     }
  498.     ti->valid = 0;
  499.     (void)tfullpath(tmpbuf, ti, "");
  500.     return Fattrib(tmpbuf, 1, attrib);
  501. }
  502.  
  503. static long ARGS_ON_STACK 
  504. tos_chown(dir, uid, gid)
  505.     fcookie *dir;
  506.     int uid, gid;
  507. {
  508.     UNUSED(dir); UNUSED(uid); UNUSED(gid);
  509.     return EINVFN;
  510. }
  511.  
  512. static long ARGS_ON_STACK 
  513. tos_chmode(fc, mode)
  514.     fcookie *fc;
  515.     unsigned mode;
  516. {
  517.     int oldattr, newattr;
  518.     long r;
  519.     struct tindex *ti = (struct tindex *)fc->index;
  520.  
  521.     oldattr = Fattrib(ti->name, 0, 0);
  522.     if (oldattr < 0)
  523.         return oldattr;
  524.  
  525.     ti->valid = 0;
  526.  
  527.     if (!(mode & S_IWUSR))
  528.         newattr = oldattr | FA_RDONLY;
  529.     else
  530.         newattr = oldattr & ~FA_RDONLY;
  531.     if (newattr != oldattr)
  532.         r = Fattrib(ti->name, 1, newattr);
  533.     else
  534.         r = 0;
  535.     return (r < 0) ? r : 0;
  536. }
  537.  
  538. static long ARGS_ON_STACK 
  539. tos_mkdir(dir, name, mode)
  540.     fcookie *dir;
  541.     const char *name;
  542.     unsigned mode;        /* ignored under TOS */
  543. {
  544.     UNUSED(mode);
  545.  
  546.     (void)tfullpath(tmpbuf, (struct tindex *)dir->index, name);
  547.     tmpindex.valid = 0;
  548.  
  549.     return Dcreate(tmpbuf);
  550. }
  551.  
  552. static long ARGS_ON_STACK 
  553. tos_rmdir(dir, name)
  554.     fcookie *dir;
  555.     const char *name;
  556. {
  557.     struct tindex *ti;
  558.  
  559.     (void)tfullpath(tmpbuf, (struct tindex *)dir->index, name);
  560.     ti = tstrindex(tmpbuf);
  561.     ti->valid = 0;
  562.  
  563.     return Ddelete(tmpbuf);
  564. }
  565.  
  566. static long ARGS_ON_STACK 
  567. tos_remove(dir, name)
  568.     fcookie *dir;
  569.     const char *name;
  570. {
  571.     struct tindex *ti;
  572.  
  573.     (void)tfullpath(tmpbuf, (struct tindex *)dir->index, name);
  574.  
  575.     ti = tstrindex(tmpbuf);
  576.     if (ti->open) {
  577.         DEBUG(("tos_remove: file is open, will be deleted later"));
  578.         if (ti->attr & FA_RDONLY)
  579.             return EACCDN;
  580.         ti->attr |= FA_DELETE;
  581.         return 0;
  582.     }
  583.     ti->valid = 0;
  584.     return Fdelete(tmpbuf);
  585. }
  586.  
  587. static long ARGS_ON_STACK 
  588. tos_getname(root, dir, pathname)
  589.     fcookie *root, *dir;
  590.     char *pathname;
  591. {
  592.     char *rootnam = ((struct tindex *)root->index)->name;
  593.     char *dirnam = ((struct tindex *)dir->index)->name;
  594.     int i;
  595.  
  596.     i = strlen(rootnam);
  597.     if (strlen(dirnam) <= i)
  598.         *pathname = 0;
  599.     else {
  600.         ksprintf(pathname, "%s", dirnam + i);
  601. /*
  602.  * BUG: must be a better way to decide upper/lower case
  603.  */
  604.         if (curproc->domain == DOM_MINT)
  605.             strlwr(pathname);
  606.     }
  607.     return 0;
  608. }
  609.  
  610. static long ARGS_ON_STACK 
  611. tos_rename(olddir, oldname, newdir, newname)
  612.     fcookie *olddir;
  613.     char *oldname;
  614.     fcookie *newdir;
  615.     const char *newname;
  616. {
  617.     char newbuf[128];
  618.     struct tindex *ti;
  619.     long r;
  620.  
  621.     (void)tfullpath(tmpbuf, (struct tindex *)olddir->index, oldname);
  622.     (void)tfullpath(newbuf, (struct tindex *)newdir->index, newname);
  623.     r = Frename(0, tmpbuf, newbuf);
  624.     if (r == 0) {
  625.         ti = tstrindex(tmpbuf);
  626.         kfree(ti->name);
  627.         ti->name = kmalloc((long)strlen(newbuf)+1);
  628.         if (!ti->name) {
  629.             FATAL("tosfs: unable to allocate space for a name");
  630.         }
  631.         strcpy(ti->name, newbuf);
  632.         ti->valid = 0;
  633.     }
  634.     return r;
  635. }
  636.  
  637. #define DIR_FLAG(x)    (x->fsstuff[0])
  638. #define STARTSEARCH    0    /* opendir() was just called */
  639. #define INSEARCH    1    /* readdir() has been called at least once */
  640. #define NMFILE        2    /* no more files to read */
  641.  
  642. #define DIR_DTA(x)    ((DTABUF *)(x->fsstuff + 2))
  643. #define DIR_NAME(x)    (x->fsstuff + 32)
  644.  
  645. /*
  646.  * The directory functions are a bit tricky. What we do is have
  647.  * opendir() do Fsfirst; the first readdir() picks up this name,
  648.  * subsequent readdir()'s have to do Fsnext
  649.  */
  650.  
  651. static long ARGS_ON_STACK 
  652. tos_opendir(dirh, flags)
  653.     DIR *dirh;
  654.     int flags;
  655. {
  656.     long r;
  657.     struct tindex *t = (struct tindex *)dirh->fc.index;
  658.  
  659.     UNUSED(flags);
  660.  
  661.     (void)tfullpath(tmpbuf, t, "*.*");
  662.  
  663.     do_setdta(DIR_DTA(dirh));
  664.  
  665.     r = Fsfirst(tmpbuf, FILEORDIR);
  666.  
  667.     if (r == 0) {
  668.         t->open++;
  669.         DIR_FLAG(dirh) = STARTSEARCH;
  670.         return 0;
  671.     } else if (r == EFILNF) {
  672.         t->open++;
  673.         DIR_FLAG(dirh) = NMFILE;
  674.         return 0;
  675.     }
  676.      return r;
  677. }
  678.  
  679. static long ARGS_ON_STACK 
  680. tos_readdir(dirh, name, namelen, fc)
  681.     DIR *dirh;
  682.     char *name;
  683.     int namelen;
  684.     fcookie *fc;
  685. {
  686.     static long index = 0;
  687.     long ret;
  688.     int giveindex = dirh->flags == 0;
  689.     struct tindex *ti;
  690.     DTABUF *dta = DIR_DTA(dirh);
  691.  
  692. again:
  693.     if (DIR_FLAG(dirh) == NMFILE)
  694.         return ENMFIL;
  695.  
  696.     if (DIR_FLAG(dirh) == STARTSEARCH) {
  697.         DIR_FLAG(dirh) = INSEARCH;
  698.     } else {
  699.         assert(DIR_FLAG(dirh) == INSEARCH);
  700.         do_setdta(dta);
  701.         ret = Fsnext();
  702.         if (ret) {
  703.             DIR_FLAG(dirh) = NMFILE;
  704.             return ret;
  705.         }
  706.     }
  707.  
  708. /* don't return volume labels from readdir */
  709.     if (dta->dta_attrib == FA_LABEL) goto again;
  710.  
  711.     fc->fs = &tos_filesys;
  712.     fc->dev = dirh->fc.dev;
  713.  
  714.     (void)tfullpath(tmpiname, (struct tindex *)dirh->fc.index, DIR_NAME(dirh));
  715.  
  716.     ti = &tmpindex;
  717.     ti->name = tmpiname;
  718.     ti->valid = 1;
  719.     ti->size = dta->dta_size;
  720.     ti->date = dta->dta_date;
  721.     ti->time = dta->dta_time;
  722.     ti->attr = dta->dta_attrib;
  723.     ti->stamp = tclock;
  724.     if (executable_extension(dta->dta_name))
  725.         ti->attr |= FA_EXEC;
  726.     fc->index = (long)ti;
  727.  
  728.     if (giveindex) {
  729.         namelen -= (int) sizeof(long);
  730.         if (namelen <= 0) return ERANGE;
  731.         *((long *)name) = index++;
  732.         name += sizeof(long);
  733.     }
  734.     strncpy(name, DIR_NAME(dirh), namelen-1);
  735.     name[namelen-1] = 0;
  736.     if (giveindex) {
  737.         strlwr(name);
  738.     }
  739.     if (strlen(DIR_NAME(dirh)) >= namelen)
  740.         return ENAMETOOLONG;
  741.     else
  742.         return 0;
  743. }
  744.  
  745. static long ARGS_ON_STACK 
  746. tos_rewinddir(dirh)
  747.     DIR *dirh;
  748. {
  749.     struct tindex *ti = (struct tindex *)dirh->fc.index;
  750.     long r;
  751.  
  752.     (void)tfullpath(tmpbuf, ti, "*.*");
  753.     do_setdta(DIR_DTA(dirh));
  754.     r = Fsfirst(tmpbuf, FILEORDIR);
  755.     if (r == 0) {
  756.         DIR_FLAG(dirh) = STARTSEARCH;
  757.     } else {
  758.         DIR_FLAG(dirh) = NMFILE;
  759.     }
  760.     return r;
  761. }
  762.  
  763. static long ARGS_ON_STACK 
  764. tos_closedir(dirh)
  765.     DIR *dirh;
  766. {
  767.     struct tindex *t = (struct tindex *)dirh->fc.index;
  768.  
  769.     if (t->open == 0) {
  770.         FATAL("t->open == 0: directory == %s", t->name);
  771.     }
  772.     --t->open;
  773.     DIR_FLAG(dirh) = NMFILE;
  774.     return 0;
  775. }
  776.  
  777. static long ARGS_ON_STACK 
  778. tos_pathconf(dir, which)
  779.     fcookie *dir;
  780.     int which;
  781. {
  782.     UNUSED(dir);
  783.  
  784.     switch(which) {
  785.     case -1:
  786.         return DP_MAXREQ;
  787.     case DP_IOPEN:
  788.         return 60;    /* we can only keep about this many open */
  789.     case DP_MAXLINKS:
  790.          return 1;    /* no hard links */
  791.     case DP_PATHMAX:
  792.         return PATH_MAX;
  793.     case DP_NAMEMAX:
  794.         return 8+3+1;
  795.     case DP_ATOMIC:
  796.         return 512;    /* we can write at least a sector atomically */
  797.     case DP_TRUNC:
  798.         return DP_DOSTRUNC;    /* DOS style file names */
  799.     case DP_CASE:
  800.         return DP_CASECONV;    /* names converted to upper case */
  801.     default:
  802.         return EINVFN;
  803.     }
  804. }
  805.  
  806. long ARGS_ON_STACK 
  807. tos_dfree(dir, buf)
  808.     fcookie *dir;
  809.     long *buf;
  810. {
  811.     return Dfree(buf, (dir->dev)+1);
  812. }
  813.  
  814. /*
  815.  * writelabel: creates a volume label
  816.  * readlabel: reads a volume label
  817.  * both of these are only guaranteed to work in the root directory
  818.  */
  819.  
  820. /*
  821.  * BUG: this should first delete any old labels, so that it will
  822.  * work with TOS <1.4
  823.  */
  824.  
  825. long ARGS_ON_STACK 
  826. tos_writelabel(dir, name)
  827.     fcookie *dir;
  828.     const char *name;
  829. {
  830.     long r;
  831.     struct tindex *ti;
  832.  
  833.     (void)tfullpath(tmpbuf, (struct tindex *)dir->index, name);
  834.     r = Fcreate(tmpbuf, FA_LABEL);
  835.     if (r < 0) return r;
  836.     (void)Fclose((int)r);
  837.     ti = tstrindex(tmpbuf);
  838.     ti->valid = 0;
  839.     return 0;
  840. }
  841.  
  842. long ARGS_ON_STACK 
  843. tos_readlabel(dir, name, namelen)
  844.     fcookie *dir;
  845.     char *name;
  846.     int namelen;
  847. {
  848.     long r;
  849.     struct tindex *ti = (struct tindex *)dir->index;
  850.  
  851.     if (ti->name[2] != 0)        /* not a root directory? */
  852.         return EFILNF;
  853.  
  854.     (void)tfullpath(tmpbuf, ti, "*.*");
  855.     do_setdta(&foo);
  856.     r = Fsfirst(tmpbuf, FA_LABEL);
  857.     if (r)
  858.         return r;
  859.     strncpy(name, foo.dta_name, namelen-1);
  860.     return (strlen(foo.dta_name) < namelen) ? 0 : ENAMETOOLONG;
  861. }
  862.  
  863. /*
  864.  * TOS creat: this doesn't actually create the file, rather it
  865.  * sets up a (fake) index for the file that will be used by
  866.  * the later tos_open call.
  867.  */
  868.  
  869. static long ARGS_ON_STACK 
  870. tos_creat(dir, name, mode, attrib, fc)
  871.     fcookie *dir;
  872.     const char *name;
  873.     unsigned mode;
  874.     int attrib;
  875.     fcookie *fc;
  876. {
  877.     struct tindex *ti;
  878.     UNUSED(mode);
  879.  
  880.     (void)tfullpath(tmpbuf, (struct tindex *)dir->index, name);
  881.  
  882.     ti = tstrindex(tmpbuf);
  883.     ti->size = 0;
  884.     ti->date = datestamp;
  885.     ti->time = timestamp;
  886.     ti->attr = attrib;
  887.     ti->valid = 1;
  888.  
  889.     fc->fs = &tos_filesys;
  890.     fc->index = (long)ti;
  891.     fc->dev = dir->dev;
  892.     return 0;
  893. }
  894.     
  895. /*
  896.  * TOS device driver
  897.  */
  898.  
  899. static DEVDRV * ARGS_ON_STACK 
  900. tos_getdev(fc, devsp)
  901.     fcookie *fc;
  902.     long *devsp;
  903. {
  904.     UNUSED(fc); UNUSED(devsp);
  905.     return &tos_device;
  906. }
  907.  
  908. static long ARGS_ON_STACK 
  909. tos_open(f)
  910.     FILEPTR *f;
  911. {
  912.     struct tindex *ti;
  913.     int mode = f->flags;
  914.     int tosmode;
  915.     long r;
  916.     extern int flk;        /* in main.c, set if _FLK cookie already present */
  917.  
  918.     ti = (struct tindex *)(f->fc.index);
  919.     assert(ti != 0);
  920.  
  921.     ti->stamp = ++tclock;
  922.     ti->valid = 0;
  923.  
  924. /* TEMPORARY HACK: change all modes to O_RDWR for files opened in
  925.  * compatibility sharing mode. This is silly, but
  926.  * allows broken TOS programs that write to read-only handles to continue
  927.  * to work (it also helps file sharing, by making the realistic assumption
  928.  * that any open TOS file can be written to). Eventually,
  929.  * this should be tuneable by the user somehow.
  930.  * ALSO: change O_COMPAT opens into O_DENYNONE; again, this may be temporary.
  931.  */
  932.     if ( (mode & O_SHMODE) == O_COMPAT ) {
  933.         f->flags = (mode & ~(O_RWMODE|O_SHMODE)) | O_RDWR | O_DENYNONE;
  934.     }
  935.  
  936. /* check to see that nobody has opened this file already in an
  937.  * incompatible mode
  938.  */
  939.     if (denyshare(ti->open, f)) {
  940.         TRACE(("tos_open: file sharing denied"));
  941.         return EACCDN;
  942.     }
  943.  
  944. /*
  945.  * now open the file; if O_TRUNC was specified, actually
  946.  * create the file anew.
  947.  * BUG: O_TRUNC without O_CREAT doesn't work right. The kernel doesn't
  948.  * use this mode, anyways
  949.  */
  950.  
  951.     if (mode & O_TRUNC) {
  952.         if (ti->open) {
  953.             DEBUG(("tos_open: attempt to truncate an open file"));
  954.             return EACCDN;
  955.         }
  956.         r = Fcreate(ti->name, ti->attr);
  957.     } else {
  958.         if (flk)
  959.             tosmode = mode & (O_RWMODE|O_SHMODE);
  960.         else
  961.             tosmode = (mode & O_RWMODE);
  962.         if (tosmode == O_EXEC) tosmode = O_RDONLY;
  963.  
  964.         r = Fopen(ti->name, tosmode );
  965.         if (r == EFILNF && (mode & O_CREAT))
  966.             r = Fcreate(ti->name, ti->attr);
  967.     }
  968.  
  969.     if (r < 0) {
  970. /* get rid of the index for the file, since it doesn't exist */
  971.         kfree(ti->name);
  972.         ti->name = 0;
  973.         ti->valid = 0;
  974.         return r;
  975.     }
  976.  
  977.     f->devinfo = r;
  978.  
  979.     f->next = ti->open;
  980.     ti->open = f;
  981.     return 0;
  982. }
  983.  
  984. static long ARGS_ON_STACK 
  985. tos_write(f, buf, bytes)
  986.     FILEPTR *f; const char *buf; long bytes;
  987. {
  988.     struct tindex *ti = (struct tindex *)f->fc.index;
  989.  
  990.     ti->valid = 0;
  991.     return Fwrite((int)f->devinfo, bytes, buf);
  992. }
  993.  
  994. static long ARGS_ON_STACK 
  995. tos_read(f, buf, bytes)
  996.     FILEPTR *f; char *buf; long bytes;
  997. {
  998.     return Fread((int)f->devinfo, bytes, buf);
  999. }
  1000.  
  1001. static long ARGS_ON_STACK 
  1002. tos_lseek(f, where, whence)
  1003.     FILEPTR *f; long where; int whence;
  1004. {
  1005.     long r;
  1006.  
  1007.     r = Fseek(where, (int)f->devinfo, whence);
  1008.     return r;
  1009. }
  1010.  
  1011. static long ARGS_ON_STACK 
  1012. tos_ioctl(f, mode, buf)
  1013.     FILEPTR *f; int mode; void *buf;
  1014. {
  1015.     LOCK t, *lck, **old;
  1016.     struct flock *fl;
  1017.     long r;
  1018.     struct tindex *ti;
  1019.     extern int flk;        /* set in main.c if _FLK already installed */
  1020.  
  1021.     if (mode == FIONREAD || mode == FIONWRITE) {
  1022.         *((long *)buf) = 1;
  1023.         return 0;
  1024.     }
  1025.     else if (mode == F_SETLK || mode == F_SETLKW || mode == F_GETLK) {
  1026.         fl = ((struct flock *)buf);
  1027.         t.l = *fl;
  1028.         switch(t.l.l_whence) {
  1029.         case 0:
  1030.             break;
  1031.         case 1:        /* SEEK_CUR */
  1032.             r = Fseek(0L, (int)f->devinfo, 1);
  1033.             t.l.l_start += r;
  1034.             break;
  1035.         case 2:
  1036.             r = Fseek(0L, (int)f->devinfo, 1);
  1037.             t.l.l_start = Fseek(t.l.l_start, (int)f->devinfo, 2);
  1038.             (void)Fseek(r, (int)f->devinfo, 0);
  1039.             break;
  1040.         default:
  1041.             DEBUG(("Invalid value for l_whence"));
  1042.             return EINVFN;
  1043.         }
  1044. /* BUG: can't lock a file starting at >2gigabytes from the beginning */
  1045.         if (t.l.l_start < 0) t.l.l_start = 0;
  1046.         t.l.l_whence = 0;
  1047.         ti = (struct tindex *)f->fc.index;
  1048.  
  1049.         if (mode == F_GETLK) {
  1050.             lck = denylock(ti->locks, &t);
  1051.             if (lck)
  1052.                 *fl = lck->l;
  1053.             else
  1054.                 fl->l_type = F_UNLCK;
  1055.             return 0;
  1056.         }
  1057.  
  1058.         if (t.l.l_type == F_UNLCK) {
  1059.         /* try to find the lock */
  1060.             old = &ti->locks;
  1061.             lck = *old;
  1062.             while (lck) {
  1063.                 if (lck->l.l_pid == curproc->pid &&
  1064.                     lck->l.l_start == t.l.l_start &&
  1065.                     lck->l.l_len == t.l.l_len) {
  1066.         /* found it -- remove the lock */
  1067.                     *old = lck->next;
  1068.                     TRACE(("tosfs: unlocked %s: %ld + %ld",
  1069.                         ti->name, t.l.l_start, t.l.l_len));
  1070.                     if (flk)
  1071.                         (void)Flock((int)f->devinfo, 1,
  1072.                             t.l.l_start, t.l.l_len);
  1073.                 /* wake up anyone waiting on the lock */
  1074.                     wake(IO_Q, (long)lck);
  1075.                     kfree(lck);
  1076.                     break;
  1077.                 }
  1078.                 old = &lck->next;
  1079.                 lck = lck->next;
  1080.             }
  1081.             return lck ?  0 : ENSLOCK;
  1082.         }
  1083.         TRACE(("tosfs: lock %s: %ld + %ld", ti->name,
  1084.             t.l.l_start, t.l.l_len));
  1085.         do {
  1086.         /* see if there's a conflicting lock */
  1087.             while ((lck = denylock(ti->locks, &t)) != 0) {
  1088.                 DEBUG(("tosfs: lock conflicts with one held by %d",
  1089.                     lck->l.l_pid));
  1090.                 if (mode == F_SETLKW) {
  1091.                     sleep(IO_Q, (long)lck);        /* sleep a while */
  1092.                 }
  1093.                 else
  1094.                     return ELOCKED;
  1095.             }
  1096.         /* if not, add this lock to the list */
  1097.             lck = kmalloc(SIZEOF(LOCK));
  1098.             if (!lck) return ENSMEM;
  1099.         /* see if other _FLK code might object */
  1100.             if (flk) {
  1101.                 r = Flock((int)f->devinfo, 0, t.l.l_start, t.l.l_len);
  1102.                 if (r) {
  1103.                     kfree(lck);
  1104.                     if (mode == F_SETLKW && r == ELOCKED) {
  1105.                         yield();
  1106.                         lck = NULL;
  1107.                     }
  1108.                     else
  1109.                         return r;
  1110.                 }
  1111.             }
  1112.         } while (!lck);
  1113.         lck->l = t.l;
  1114.         lck->l.l_pid = curproc->pid;
  1115.         lck->next = ti->locks;
  1116.         ti->locks = lck;
  1117.     /* mark the file as being locked */
  1118.         f->flags |= O_LOCK;
  1119.         return 0;
  1120.     }
  1121.     return EINVFN;
  1122. }
  1123.  
  1124. static long ARGS_ON_STACK 
  1125. tos_datime(f, timeptr, rwflag)
  1126.     FILEPTR *f;
  1127.     short *timeptr;
  1128.     int rwflag;
  1129. {
  1130.     if (rwflag) {
  1131.         struct tindex *ti = (struct tindex *)f->fc.index;
  1132.         ti->valid = 0;
  1133.     }
  1134.     return Fdatime(timeptr, (int)f->devinfo, rwflag);
  1135. }
  1136.  
  1137. static long ARGS_ON_STACK 
  1138. tos_close(f, pid)
  1139.     FILEPTR *f;
  1140.     int pid;
  1141. {
  1142.     LOCK *lck, **oldl;
  1143.     struct tindex *t;
  1144.     FILEPTR **old, *p;
  1145.     long r = 0;
  1146.     extern int flk;        /* set in main.c */
  1147.  
  1148.     t = (struct tindex *)(f->fc.index);
  1149. /* if this handle was locked, remove any locks held by the process
  1150.  */
  1151.     if (f->flags & O_LOCK) {
  1152.         TRACE(("tos_close: releasing locks (file mode: %x)", f->flags));
  1153.         oldl = &t->locks;
  1154.         lck = *oldl;
  1155.         while (lck) {
  1156.             if (lck->l.l_pid == pid) {
  1157.                 *oldl = lck->next;
  1158.                 if (flk)
  1159.                     (void)Flock((int)f->devinfo, 1,
  1160.                         lck->l.l_start, lck->l.l_len);
  1161.                 wake(IO_Q, (long)lck);
  1162.                 kfree(lck);
  1163.             } else {
  1164.                 oldl = &lck->next;
  1165.             }
  1166.             lck = *oldl;
  1167.         }
  1168.     }
  1169.  
  1170.     if (f->links <= 0) {
  1171. /* remove f from the list of open file pointers on this index */
  1172.         t->valid = 0;
  1173.         old = &t->open;
  1174.         p = t->open;
  1175.         while (p && p != f) {
  1176.             old = &p->next;
  1177.             p = p->next;
  1178.         }
  1179.         assert(p);
  1180.         *old = f->next;
  1181.         f->next = 0;
  1182.         r = Fclose((int)f->devinfo);
  1183.  
  1184. /* if the file was marked for deletion, delete it */
  1185.         if (!t->open) {
  1186.             if (t->attr & FA_DELETE) {
  1187.                 (void)Fdelete(t->name);
  1188.                 t->name = 0;
  1189.             }
  1190.         }
  1191.     }
  1192.     return r;
  1193. }
  1194.  
  1195. /*
  1196.  * check for disk change: called by the kernel if Mediach returns a
  1197.  * non-zero value
  1198.  */
  1199.  
  1200. long ARGS_ON_STACK 
  1201. tos_dskchng(drv)
  1202.     int drv;
  1203. {
  1204.     char dlet;
  1205.     int i;
  1206.     struct tindex *ti;
  1207.  
  1208.     dlet = 'A' + drv;
  1209.     ti = gl_ti;
  1210.     for (i = 0; i < NUM_INDICES; i++, ti++) {
  1211.         if (ti->name && ti->name[0] == dlet) {
  1212.             kfree(ti->name);
  1213.             ti->name = 0;
  1214.         }
  1215.     }
  1216.     tmpindex.valid = 0;
  1217. /*
  1218.  * OK, make sure that GEMDOS knows to look for a change if we
  1219.  * ever use this drive again.
  1220.  */
  1221.     drvchanged[drv] = 1;
  1222.     return 1;
  1223. }
  1224.  
  1225. /*
  1226.  * utility function: sets the TOS DTA, and also records what directory
  1227.  * this was in. This is just to save us a call into the kernel if the
  1228.  * correct DTA has already been set.
  1229.  */
  1230.  
  1231. static void
  1232. do_setdta(dta)
  1233.     DTABUF *dta;
  1234. {
  1235.     if (dta != lastdta) {
  1236.         Fsetdta(dta);
  1237.         lastdta = dta;
  1238.     }
  1239. }
  1240.  
  1241. /*
  1242.  * routines for forcing a media change on drive "drv"
  1243.  */
  1244.  
  1245. static int chdrv;
  1246.  
  1247. /* new Getbpb function: when this is called, all the other
  1248.  * vectors can be un-installed
  1249.  */
  1250.  
  1251. static long ARGS_ON_STACK (*Oldgetbpb) P_((int));
  1252. static long ARGS_ON_STACK (*Oldmediach) P_((int));
  1253. static long ARGS_ON_STACK (*Oldrwabs) P_((int, void *, int, int, int, long));
  1254.  
  1255. static long  ARGS_ON_STACK
  1256. Newgetbpb(d)
  1257.     int d;
  1258. {
  1259.     if (d == chdrv) {
  1260.         *((Func *)0x472L) = Oldgetbpb;
  1261.         *((Func *)0x476L) = Oldrwabs;
  1262.         *((Func *)0x47eL) = Oldmediach;
  1263. #if 0
  1264.         return 0;
  1265. #endif
  1266.     }
  1267.     return (*Oldgetbpb)(d);
  1268. }
  1269.  
  1270. static long ARGS_ON_STACK
  1271. Newmediach(d)
  1272.     int d;
  1273. {
  1274.     if (d == chdrv)
  1275.         return 2;
  1276.     return (*Oldmediach)(d);
  1277. }
  1278.  
  1279. static long ARGS_ON_STACK
  1280. Newrwabs(d, buf, a, b, c, l)
  1281.     int d;
  1282.     void *buf;
  1283.     int a, b, c;
  1284.     long l;
  1285. {
  1286.     if (d == chdrv)
  1287.         return E_CHNG;
  1288.     return (*Oldrwabs)(d, buf, a, b, c, l);
  1289. }
  1290.  
  1291. static void
  1292. force_mediach(d)
  1293.     int d;
  1294. {
  1295.     static char fname[] = "X:\\*.*";
  1296.  
  1297.     TRACE(("tosfs: disk change drive %c", d+'A'));
  1298.  
  1299.     chdrv = d;
  1300.     Oldrwabs = *((Func *)0x476L);
  1301.     if (Oldrwabs == Newrwabs) {
  1302.         ALERT("tosfs: error in media change code");
  1303.     } else {
  1304.         *((Func *)0x476L) = Newrwabs;
  1305.         Oldmediach = *((Func *)0x47eL);
  1306.         *((Func *)0x47eL) = Newmediach;
  1307.         Oldgetbpb = *((Func *)0x472L);
  1308.         *((Func *)0x472L) = Newgetbpb;
  1309.     }
  1310.  
  1311.     fname[0] = d + 'A';
  1312.     Fsfirst(fname, 0);
  1313.     drvchanged[d] = 0;
  1314. }
  1315.